/*
 * Copyright 2007 Seok-Ho, Yang
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package kr.javanese.devtools.m2wtp.builder;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import kr.javanese.devtools.m2wtp.common.BuildPathManager;
import kr.javanese.devtools.m2wtp.common.M2WTPConstants;
import kr.javanese.devtools.m2wtp.common.MavenClassPathList;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.util.URI;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jem.util.emf.workbench.ProjectUtilities;
import org.eclipse.jst.j2ee.application.internal.operations.ClassPathSelection;
import org.eclipse.jst.j2ee.application.internal.operations.ClasspathElement;
import org.eclipse.jst.j2ee.internal.common.ClasspathModel;
import org.eclipse.wst.common.componentcore.ComponentCore;
import org.eclipse.wst.common.componentcore.internal.impl.ModuleURIUtil;
import org.eclipse.wst.common.componentcore.internal.resources.VirtualArchiveComponent;
import org.eclipse.wst.common.componentcore.internal.resources.VirtualComponent;
import org.eclipse.wst.common.componentcore.internal.util.IModuleConstants;
import org.eclipse.wst.common.componentcore.resources.IVirtualComponent;
import org.eclipse.wst.common.componentcore.resources.IVirtualReference;
import org.eclipse.wst.common.project.facet.core.IFacetedProject;
import org.eclipse.wst.common.project.facet.core.IProjectFacet;
import org.eclipse.wst.common.project.facet.core.ProjectFacetsManager;
import org.maven.ide.eclipse.Maven2Plugin;

/**
 * @author Seok-Ho, Yang (KR)
 * @author Cyril Joui (FR)
 */
public class PomProcessor {

	public void process(IFile pom, IProgressMonitor monitor) {
		IJavaProject javaProject = JavaCore.create(pom.getProject());

		if (null != javaProject) {
			IPath m2RepoPath = JavaCore.getClasspathVariable("M2_REPO");
			if (m2RepoPath == null) {
				Maven2Plugin.getDefault().getLog().log(new Status(
						IStatus.ERROR, M2WTPConstants.PLUGIN_ID, IStatus.OK, "M2_REPO is not specified.", new RuntimeException()));
			}
			// initialize WTP Classpath Model
			ClasspathModel model = new ClasspathModel(null);
			model.setProject(pom.getProject());

			try {
				// remove all M2_REPO Module Dependencies
				removeAllModulesFromM2(model, pom.getProject());

				// load project m2wtp properties configuration file
				IFile m2wtpConfigFile = pom.getProject().getFile("m2wtp.properties");
				Properties m2wtpConfigProperties = new Properties();
				List<String> excludedPatterns = new LinkedList<String>();
				if (m2wtpConfigFile != null
						&& m2wtpConfigFile.exists()) {
					m2wtpConfigProperties.load(m2wtpConfigFile.getContents());
					if (m2wtpConfigProperties.containsKey("excludeJars")) {
						String[] excludes = m2wtpConfigProperties.getProperty("excludeJars").split(",");
						for (String exclude : excludes) {
							excludedPatterns.add(exclude);
						}
					}
				}
				
				MavenClassPathList mavenClassPathList = BuildPathManager.getClasspathList(pom, monitor);
				Set<IClasspathEntry> classpathToExport = new HashSet<IClasspathEntry>();
				classpathToExport.addAll(mavenClassPathList.getCompileDependencies());
				classpathToExport.addAll(mavenClassPathList.getRuntimeDependencies());

				// dependencies to export in Web Libraries Container
				for(IClasspathEntry entry : classpathToExport) {
					IPath path = entry.getPath();
					if (!excludedPatterns.contains(path.lastSegment())) {
						if(entry.getEntryKind() != IClasspathEntry.CPE_PROJECT) {
							if (m2RepoPath.isPrefixOf(path)) {
								IPath relPath = path.removeFirstSegments(m2RepoPath.segmentCount());
								addLibraryModuleFromM2(model, relPath);
							}
						} else {
							addProjectModuleFromM2(model, entry);
						}
					} else {
						System.out.println("excluded path: " + path);
					}

				}
				model.getClassPathSelectionForWLPs().setModified(true);

				// dependencies not to export (tests, provided ...)
				Set<IClasspathEntry> classpathNotToExport = new HashSet<IClasspathEntry>();
				classpathNotToExport.addAll(mavenClassPathList.getProvidedDependencies());
				classpathNotToExport.addAll(mavenClassPathList.getTestDependencies());
				IClasspathEntry[] currentClasspathEntries = javaProject.getRawClasspath();
				IClasspathEntry[] newClasspathEntries = new IClasspathEntry[currentClasspathEntries.length
				                                                            + classpathNotToExport.size()];
				System.arraycopy(currentClasspathEntries, 0, newClasspathEntries, 0, currentClasspathEntries.length);
				int index = 0;
				for(IClasspathEntry entry : classpathNotToExport) {
					newClasspathEntries[currentClasspathEntries.length + index] = entry;
					index++;
				}
				javaProject.setRawClasspath(newClasspathEntries, monitor);
			} catch(JavaModelException jme) {
				Maven2Plugin.getDefault().getLog().log(new Status(
						IStatus.ERROR, M2WTPConstants.PLUGIN_ID, IStatus.OK, "Javanese Web Library POM Processing Failed", jme));
			} catch(CoreException ce) {
				Maven2Plugin.getDefault().getLog().log(new Status(
						IStatus.ERROR, M2WTPConstants.PLUGIN_ID, IStatus.OK, "Javanese Web Library POM Processing Failed", ce));
			} catch(Exception ex) {
				Maven2Plugin.getDefault().getLog().log(new Status(
						IStatus.ERROR, M2WTPConstants.PLUGIN_ID, IStatus.OK, "Javanese Web Library POM Processing Failed", ex));
			} finally {
				if (model!=null) model.dispose();
			}
		}
	}

	private void addProjectModuleFromM2(ClasspathModel model, IClasspathEntry entry) {
		for (Object obj : model.getClassPathSelectionForWLPs().getClasspathElements()) {
			ClasspathElement element = (ClasspathElement)obj;
			if (element.getProject()!=null && element.getProject().getFullPath().equals(entry.getPath())) {
				element.setSelected(true);
				try {
					addReferencedComponents(model, element.getComponent());
					ProjectUtilities.addReferenceProjects(model.getProject(), element.getProject());
				} catch(CoreException ce) {
					Maven2Plugin.getDefault().getLog().log(new Status(
							IStatus.ERROR, M2WTPConstants.PLUGIN_ID, IStatus.OK, "A Project Reference can't be added", ce));
				}
			}
		}
	}

	public static boolean isProjectOfType(IProject project, String typeID) {
		IFacetedProject facetedProject = null;
		try {
			facetedProject = ProjectFacetsManager.create(project);
		} catch (CoreException e) {
			return false;
		}

		if (facetedProject != null && ProjectFacetsManager.isProjectFacetDefined(typeID)) {
			IProjectFacet projectFacet = ProjectFacetsManager.getProjectFacet(typeID);
			return projectFacet != null && facetedProject.hasProjectFacet(projectFacet);
		}
		return false;
	}

	private ClasspathElement createClassPathElement(IVirtualComponent archiveComp, String unresolvedName) {

		URI uri = URI.createURI(ModuleURIUtil.getHandleString(archiveComp));
		ClasspathElement element = new ClasspathElement(uri);
		element.setValid(false);
		element.setSelected(true);
		element.setRelativeText(unresolvedName);
		element.setText(unresolvedName);
		element.setEarProject(null);
		return element;
	}

	private ClassPathSelection createClassPathSelectionForExternalJar(ClasspathElement element) {
		ClassPathSelection selection = new ClassPathSelection();
		selection.getClasspathElements().add(element);
		return selection;
	}

	private void createRef(ClasspathModel model, String aComponentName){
		IVirtualComponent archive = ComponentCore.createArchiveComponent(model.getComponent().getProject(),
				aComponentName);

		// TODO : check if archive component already exists
		IVirtualReference ref = ComponentCore.createReference(model.getComponent(),
				archive,
				new Path("/WEB-INF/lib")); //$NON-NLS-1$
		model.getComponent().addReferences(new IVirtualReference [] { ref });

		// TODO : Classpath checks (already added ...)
		ClasspathElement element = createClassPathElement(archive, archive.getName());
		ClassPathSelection selection = createClassPathSelectionForExternalJar(element);
		List<ClasspathElement> classpathElements = model.getClassPathSelectionForWLPs().getClasspathElements();
		if (!model.getClassPathSelectionForWLPs().getClasspathElements().contains(element)) {
			model.getClassPathSelectionForWLPs().getClasspathElements().add(element);
		} else {
			System.out.println("element : " + element + " already in references classpath");
		}
	}

	private boolean srcComponentContainsReference(IVirtualComponent sourceComp, IVirtualComponent comp) {
		if ((sourceComp != null && sourceComp.getProject() != null) && (comp != null && comp.getProject() != null)) {

			IVirtualReference[] existingReferences = sourceComp.getReferences();
			IVirtualComponent referencedComponent = null;
			if (existingReferences != null) {
				for (int i = 0; i < existingReferences.length; i++) {
					if (existingReferences[i] != null ) {
						referencedComponent = existingReferences[i].getReferencedComponent();
						if (referencedComponent != null && referencedComponent.equals(comp)  )
								return true;
					}
				}
			}
		}
		return false;
	}

	protected void addReferencedComponents(ClasspathModel model, IVirtualComponent comp) {
		IVirtualComponent sourceComp = (IVirtualComponent) model.getComponent();
		if (!srcComponentContainsReference(sourceComp, comp)) {
			IVirtualReference ref = ComponentCore.createReference(sourceComp, comp);
			// FIXME: How can I get deploy-path?
			ref.setRuntimePath(new Path("/WEB-INF/lib"));
			sourceComp.addReferences(new IVirtualReference [] { ref });
		}
	}

	private void addLibraryModuleFromM2(ClasspathModel model, IPath relPath) {
		IProject project = model.getProject();
		if (isProjectOfType(project, IModuleConstants.JST_WEB_MODULE)) {
			IPath variablePath = new Path("M2_REPO").append(relPath);

			String type = VirtualArchiveComponent.VARARCHIVETYPE + IPath.SEPARATOR;
			IPath resolvedPath = JavaCore.getResolvedVariablePath(variablePath);
			java.io.File file = new java.io.File(resolvedPath.toOSString());
			if (file.isFile() && file.exists()) {
				createRef(model, type + variablePath.toString());
			} else {
				Maven2Plugin.getDefault().getLog().log(new Status(
						IStatus.ERROR, M2WTPConstants.PLUGIN_ID, IStatus.OK, "Can't find library file "+file.toString(), new RuntimeException()));
			}
		}
	}

	private void removeAllModulesFromM2(ClasspathModel model, IProject project) {
		if (isProjectOfType(project, IModuleConstants.JST_WEB_MODULE)) {
			String type = VirtualArchiveComponent.VARARCHIVETYPE + IPath.SEPARATOR;
//			IPath variablePath = new Path(type + "M2_REPO");
			String varPrefix = type + "M2_REPO";
			Object[] objs = model.getClassPathSelectionForWLPs().getClasspathElements().toArray();
			for (Object obj : objs) {
				ClasspathElement element = (ClasspathElement)obj;
				if (element.getText().startsWith(varPrefix)) {
					element.setSelected(false);
					IVirtualReference[] refs = model.getComponent().getReferences();
					// FIXME: manage references
					for (IVirtualReference ref : refs) {
						if (ref.getReferencedComponent().getName().equals(element.getText())) {
							((VirtualComponent)model.getComponent()).removeReference(ref);
						}
					}
					model.getClassPathSelectionForWLPs().getClasspathElements().remove(element);
				}
			}
			model.getClassPathSelectionForWLPs().setModified(true);
//			for (IVirtualReference ref : refs) {
//				IVirtualComponent archive = ref.getReferencedComponent();
//				if (archive instanceof J2EEModuleVirtualArchiveComponent) {
//					IPath path = new Path(((J2EEModuleVirtualArchiveComponent)archive).getDeployedName());
//					if (variablePath.isPrefixOf(path)) {
//						for (Object obj : model.getClassPathSelectionForWLPs().getClasspathElements()) {
//							ClasspathElement element = (ClasspathElement)obj;
//							if (archive==element.getComponent()) {
//								element.setSelected(false);
//							}
//						}
//						//ClasspathElement element = createClassPathElement(archive, archive.getName());
//					}
//				}
//			}
		}
	}
}
